package com.yc.javaee.网络爬虫;

import com.yc.common.util.DBHelper;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import redis.clients.jedis.Jedis;

import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Set;

/*
    爬取 bbs 页面数据:
        id 标题, 时间 , 公示时间 , 内容, 下载链接

 */
public class BbsSpider {
    /**
     * 地址:  1.扫描的地址, 它包含要采集数据的链接
     * 2.采集的地址
     */
    //  Collections.synchronizedSet 将非同步set集合 转成 同步set集合
    Set<String> toScanSet = Collections.synchronizedSet(new LinkedHashSet<>());  // 待扫描地址集合
    Set<String> toSaveSet = Collections.synchronizedSet(new LinkedHashSet<>());  // 待采集地址集合
    Set<String> scanedSet = Collections.synchronizedSet(new LinkedHashSet<>());  // 已扫描地址集合
    Set<String> savedSet = Collections.synchronizedSet(new LinkedHashSet<>());  //  已待采集地址集合

    public static void main(String[] args) throws IOException, InterruptedException {
        //new BbsSpider().save("http://hyfc365.com/bbs/show/4546.html");
        BbsSpider bbsSpider = new BbsSpider();
//        bbsSpider.scan("http://hyfc365.com/bbs/",
//                "/bbs/p0/\\d+\\.html",
//                "/bbs/show/\\d+\\.html");
//        System.out.println(bbsSpider.toScanSet);
//        System.out.println(bbsSpider.toSaveSet);
        bbsSpider.start("http://hyfc365.com/bbs/", "/bbs/p0/\\d+\\.html",
                "/bbs/show/\\d+\\.html");
    }

    // 域名
    private String host;

    public void start(String scanUrl, String scanRegExp, String saveRegExp) throws InterruptedException {
        // 加载Redis数据
        loadFormRedis();

        // 提取域名
        host = scanUrl.replaceAll("(https?://[\\w\\.]+/).*", "$1");

        // 判断初始地址是否已经被爬取, 如果没有则表示是第一个爬取
        if(! toScanSet.contains(scanUrl) && ! scanedSet.contains(scanUrl)){
            // 将初始地址存入 toScanSet
            toScanSet.add(scanUrl);
            addToRedis("toScanSet", scanUrl);
        }

        // 启动扫描线程
        Thread t1 = new Thread(() -> {
            while (true) {
                // 随机取出一个扫描地址
                String address = toScanSet.iterator().next();
                String realAddress = address;
                try {
                    if (address.startsWith("/")) {
                        // 绝对路径 ==> 拼接域名
                        realAddress = host + realAddress;
                    } else if (address.startsWith("http")) {
                        // 完整路径 ==> 不做任何处理
                    } else if (address.startsWith("../") || address.startsWith("./")) {
                        //  ./当前文件 ../上级目录 ==> 根据当前所在的路径进行计算
                    }
                    scan(realAddress, scanRegExp, saveRegExp);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                // 将已经扫描完的地址 移入到 scanedSet
                toScanSet.remove(address);
                delToRedis("toScanSet", address);
                scanedSet.add(address);
                addToRedis("scanedSet", address);
                /**
                 * 考虑反扒机制, 刻意扫描速度, 做延迟
                 */
            }
        });
        t1.setDaemon(true); // 精灵线程
        t1.start();

        Thread t2 = new Thread(() -> {
            while (true) {
                synchronized (toSaveSet) {
                    while (toSaveSet.isEmpty()) {
                        toSaveSet.notifyAll();
                        try {
                            toSaveSet.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 随机取出一个扫描地址
                    String address = toSaveSet.iterator().next();
                    String realAddress = address;
                    try {
                        if (address.startsWith("/")) {
                            // 绝对路径 ==> 拼接域名
                            realAddress = host + realAddress;
                        } else if (address.startsWith("http")) {
                            // 完整路径 ==> 不做任何处理
                        } else if (address.startsWith("../") || address.startsWith("./")) {
                            //  ./当前文件 ../上级目录 ==> 根据当前所在的路径进行计算
                        }
                        save(realAddress);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    // 将已经扫描完的地址 移入到 scanedSet
                    toSaveSet.remove(address);
                    delToRedis("toSaveSet", address);
                    savedSet.add(address);
                    addToRedis("savedSet", address);
                    /**
                     * 考虑反扒机制, 刻意扫描速度, 做延迟
                     */
                }
            }
        });
        t2.setDaemon(true);
        t2.start();

        Thread.sleep(10000);
        System.out.println("============== 待扫描地址 ==============");
        toScanSet.forEach(u -> {
            System.out.println(u);
        });

        System.out.println("\n\n============== 已扫描地址 ==============");
        scanedSet.forEach(u -> {
            System.out.println(u);
        });

        System.out.println("============== 待采集地址 ==============");
        toSaveSet.forEach(u -> {
            System.out.println(u);
        });

    }

    /**
     * 扫描网页
     * address 扫描的地址
     * scanRegExp 收集要扫描的地址的 正则表达式
     * saveRegExp 收集要采集数据的地址的 正则表达式
     */
    public void scan(String address, String scanRegExp, String saveRegExp)
            throws IOException {
        URL url = new URL(address);
        Document doc = Jsoup.parse(url, 5000);
        // 提取页面中要扫描的超链接
        Elements as = doc.select("a");
        as.forEach(a -> {
            String href = a.attr("href");
            // 判断是否是需要扫描的地址
            if (href.matches(scanRegExp)) {
                // 判断是否已经扫描或采集
                if (!scanedSet.contains(href) && !toScanSet.contains(href)) {
                    toScanSet.add(href);
                    addToRedis("toScanSet",href);
                }
            }
            // 判断是否是需要采集的地址
            if (href.matches(saveRegExp)) {
                // 判断是否已经扫描或采集
                if (!savedSet.contains(href) && !toSaveSet.contains(href)) {
                    synchronized (toSaveSet) {
                        toSaveSet.add(href);
                        addToRedis("toSaveSet", href);
                        toSaveSet.notifyAll();
                    }
                }
            }
        });
    }

    //  String address= "http://hyfc365.com/bbs/show/4546.html";
    public void save(String address) throws IOException {
        URL url = new URL(address);
        Document doc = Jsoup.parse(url, 8000);

        String id = address.replaceAll(".+/(\\d+)\\.html", "$1");
        String title = doc.selectFirst(".layui-main header").text();
        String addTime = doc.selectFirst(".layui-main div.layui-row").text();
        //String pubTime = doc.selectFirst(".layui-main article>p:nth-child(1)").text();
        // 提取 html 源码
        String content = doc.selectFirst(".layui-main article").html();
        // 提取标签属性值
        //String downlink = doc.selectFirst(".layui-main article>p:nth-child(3)>a")
        //        .attr("href");
        //String downtext = doc.selectFirst(".layui-main article>p:nth-child(3)>a").text();
        String sql = "insert into hyfc_bbs values(?,?,?,?,?)";
        try{
            DBHelper.update(sql, id, title, addTime, content, address);
        } catch ( Exception e){
            System.out.println(e.getMessage() + ", 继续爬取");
        }

    }

    Jedis jedis = new Jedis();
    // 从reids读取数据 启动时读一次
    public void loadFormRedis() {
        toSaveSet.addAll(jedis.smembers("toSaveSet"));
        savedSet.addAll(jedis.smembers("savedSet"));
        toScanSet.addAll(jedis.smembers("toScanSet"));
        scanedSet.addAll(jedis.smembers("scanedSet"));
    }

    // 每次集合操作都执行一次
    // 添加地址
    public void addToRedis(String setName, String url) {
        jedis.sadd(setName, url);
    }

    // 移除地址
    public void delToRedis(String setName, String url) {
        jedis.srem(setName, url);
    }

}